// START: header
tree grammar ModelDef;
options {
  tokenVocab = Model;
  ASTLabelType = ModelAST;
  filter = true;
}
@members {
    SymbolTable symtab;
    Scope currentScope;
    public ModelDef(TreeNodeStream input, SymbolTable symtab) {
        this(input);
        this.symtab = symtab;
        currentScope = symtab.globals;
    }
}
@header {package samples;}

topdown
    :   enterClass
    |	enterInterface
    |   fieldDeclaration
    |   methodDeclaration
    ;

bottomup
    :	exitClass
    |	exitInterface
    |	exitMethod
    ;

enterClass
    :    ^('class' name=IDENTIFIER (^('implements' sup=.))? .)
        { // def class but leave superclass blank until ref phase
        System.out.println("line "+$name.getLine()+
                           ": def class "+$name.text);
        // record scope in AST for next pass
        if ( $sup!=null ) $sup.scope = currentScope; 
        ClassSymbol cs = new ClassSymbol($name.text,currentScope,null,"class");
        cs.def = $name;           // point from symbol table into AST
        $name.symbol = cs;        // point from AST into symbol table
        currentScope.define(cs);  // def class in current scope
        currentScope = cs;        // set current scope to class scope
        }
    ;

exitClass
    :   'class'
        {
        System.out.println("members: "+currentScope);
        currentScope = currentScope.getEnclosingScope();    // pop scope
        }
    ;

enterInterface
    :    ^('interface' name=IDENTIFIER (^('extends' sup=.))? .)
        { // def class but leave superclass blank until ref phase
        System.out.println("line "+$name.getLine()+
                           ": def interface "+$name.text);
        // record scope in AST for next pass
        if ( $sup!=null ) $sup.scope = currentScope; 
        ClassSymbol cs = new ClassSymbol($name.text,currentScope,null,"interface");
        cs.def = $name;           // point from symbol table into AST
        $name.symbol = cs;        // point from AST into symbol table
        currentScope.define(cs);  // def class in current scope
        currentScope = cs;        // set current scope to class scope
        }
    ;

exitInterface
    :   'interface'
        {
        System.out.println("members: "+currentScope);
        currentScope = currentScope.getEnclosingScope();    // pop scope
        }
    ;
    
methodDeclaration
    :   ^(METHOD_DECL type ID=IDENTIFIER)
        {
        System.out.println("line "+$ID.getLine()+": def method "+$ID.text);
        $type.tsym.scope = currentScope;
        MethodSymbol ms = new MethodSymbol($ID.text,null,currentScope);
        ms.def = $ID;            // track AST location of def's ID
        $ID.symbol = ms;         // track in AST
        currentScope.define(ms); // def method in globals
        currentScope = ms;       // set current scope to method scope
        }
    ;

exitMethod
    :   METHOD_DECL
        {
        System.out.println("args: "+currentScope);
        currentScope = currentScope.getEnclosingScope();    // pop arg scope
        }
    ;

fieldDeclaration // global, parameter, or local variable
    :   ^(FIELD_DECL type ID=IDENTIFIER)
        {
        System.out.println("line "+$ID.getLine()+": def "+$type.tsym+" field "+$ID.text);
        $type.tsym.scope = currentScope;
        VariableSymbol vs = new VariableSymbol($ID.text,null);
        vs.def = $ID;            // track AST location of def's ID
        $ID.symbol = vs;         // track in AST
        currentScope.define(vs);
        }
    ;

type returns [ModelAST tsym]
    :   ^((REL_DECL|TYPE_DECL) ID=IDENTIFIER)
		{
	    // get scope from AST; use to resolve type name and save it in AST
	    $tsym = $ID; // return Type from this rule
		}
    ;